package com.yjz.middleware.logback;

import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.classic.spi.StackTraceElementProxy;
import ch.qos.logback.core.UnsynchronizedAppenderBase;
import com.fasterxml.jackson.databind.ObjectMapper;
//import com.quixmart.common.controller.ExceptionController;
//import com.quixmart.common.exception.ServiceException;
//import com.quixmart.common.exception.WebException;
import lombok.Builder;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.web.client.RestTemplate;

import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Objects;

/**
 * @desc: 往钉钉群发送error日志
 * @author: yuanjz@deepblueai.com
 * @date: 2019/11/13 10:01
 */
@Slf4j
public class DingTalkAppender extends UnsynchronizedAppenderBase<ILoggingEvent> {

  private static final String url = "https://oapi.dingtalk.com/robot/send?access_token=%s";
  private static final String I18ClassNmae = "com.quixmart.sys.i18n.support.context.I18NMessageResource";
  /**
   * 钉钉群的名字,没实际作用,仅仅作为标识
   */
  @Setter
  @Getter
  private String  dingTalkGroupName;
  /**
   * 被@的群成员的手机号码,多个号码则逗号分隔
   */
  @Setter
  @Getter
  private String atMobiles;
  /**
   * 是否@所有人,如果@所有人则atMobiles字段无效
   */
  @Getter
  @Setter
  private Boolean atAll;
  /**
   * 服务的环境标识:native,dev,uat,test,prod 等
   */
  @Setter
  @Getter
  private String env;
  /**
   * 钉钉群的accessToken
   */
  @Getter
  @Setter
  private String accessToken;
  /**
   * 要记录的异常堆栈行数
   */
  @Setter
  @Getter
  private Integer traceLine;

  @Override
  protected void append(ILoggingEvent event) {
    if (Level.ERROR == event.getLevel()) {
      //忽略找不到国际化记录的error日志
      if (I18ClassNmae.equals(event.getLoggerName())) {
        return;
      }
      //忽略抛出的自定义ServiceException,WebException异常
      //todo 自己定义的自定义异常
//      if (ExceptionController.class.getName().equals(event.getLoggerName())) {
//        if (Objects.nonNull(event.getThrowableProxy())) {
//          if (ServiceException.class.getName().equals(event.getThrowableProxy().getClassName())
//            || WebException.class.getName().equals(event.getThrowableProxy().getClassName())) {
//            return;
//          }
//        }
//      }
      //非自定义异常发送钉钉
      if (Objects.nonNull(event.getThrowableProxy())) {
        //标题
        String title = String.format(env + "【%s】", event.getLoggerContextVO().getName());
        //日志时间
        Long logTime = event.getTimeStamp();
        String logTimeStr = DateTimeFormatter.ofPattern("YYYY-MM-dd HH:mm:ss.SSS").format(LocalDateTime.ofInstant(Instant.ofEpochMilli(logTime), ZoneId.of("Asia/Shanghai")));
        //异常类型
        String exceptionType = event.getThrowableProxy().getClassName();
        //异常快照
        String exceptionSnapshoot = event.getThrowableProxy().getMessage();
        //内容
        StackTraceElementProxy[] errorStack = event.getThrowableProxy().getStackTraceElementProxyArray();
        //构造钉钉请求体
        Message message = messageGenerator(atMobiles, title, logTimeStr, errorStack, exceptionType, exceptionSnapshoot);
        //发消息
        sendToDingTalk(message);
      }
    }

  }


  /**
   * @param atMobiles          被@的人的号码
   * @param title              标题
   * @param logTime            日志产生的时间
   * @param errorStackArr      错误栈
   * @param exceptionType      异常类型,如: java.lang.ArithmeticException
   * @param exceptionSnapShoot 异常快照,如:/ by zero
   * @return
   */
  private Message messageGenerator(String atMobiles, String title, String logTime, StackTraceElementProxy[] errorStackArr, String exceptionType, String exceptionSnapShoot) {
    int i = 0;
    StringBuilder content = new StringBuilder(atMobiles + "\n\r");
    content.append(title + "报错\n\r");
    content.append("日志时间:" + logTime + "\n\r");
    content.append(exceptionType + ": " + exceptionSnapShoot + "\n\r\r");
    for (StackTraceElementProxy trace : errorStackArr) {
      //默认打印20行的堆栈
      if (i < (Objects.nonNull(traceLine) ? traceLine : 20)) {
        content.append(trace.getSTEAsString()).append("\n");
      } else {
        break;
      }
      i++;
    }
    //被@的人的号码
    List<String> mobiles = Arrays.asList(Objects.nonNull(atMobiles) ? atMobiles.split(",") : null);
    //@的人
    At at = At.builder().atMobiles(mobiles).isAtAll(atAll).build();
    //消息内容
    Text text = Text.builder().content(content.toString()).build();

    Message message = Message.builder().msgtype("text").text(text).at(at).build();

    return message;
  }

  /**
   * @param message 钉钉消息
   */
  private void sendToDingTalk(Message message) {
    try {
      String messageJson = new ObjectMapper().writeValueAsString(message);
      HttpHeaders headers = new HttpHeaders();
      headers.setContentType(MediaType.APPLICATION_JSON_UTF8);
      HttpEntity<String> formEntity = new HttpEntity<>(messageJson, headers);
      String formatUrl = String.format(url, accessToken);
      new RestTemplate().postForObject(formatUrl, formEntity, Map.class);
    } catch (Exception e) {
      log.warn("钉钉消息发送失败", e);
    }
  }
}

@Data
@Builder
class Message {
  private String msgtype;
  private Text text;
  private At at;
}

@Data
@Builder
class Text {
  private String content;
}

@Data
@Builder
class At {
  private List<String> atMobiles;
  private Boolean isAtAll;
}

